package com.cuke.entityhelper.actions.base;

import com.cuke.entityhelper.entity.Parameter;
import com.google.common.collect.Sets;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.cuke.entityhelper.utils.*;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

/**
 * 针对类的实例声明后，一次性生成所有属性的赋值语句，并给默认值，或不给值，或给随机值(用于测试)
 */
public abstract class BaseGenerateAllSetter extends AnAction {

    //abstract protected boolean hasDefaultValue();

    protected boolean hasDefaultValue() {
        return false;
    }

    protected boolean grantRandomValue() {
        return false;
    }

    protected boolean isWithMethod() {
        return false;
    }

    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        Project project = e.getProject();
        Editor editor = e.getData(PlatformDataKeys.EDITOR);
        PsiFile file = e.getData(PlatformDataKeys.PSI_FILE);
        if (project == null || editor == null || file == null) {
            return;
        }
        //获取光标所在元素
        PsiElement element = PsiElementUtils.getElement(editor, file);
        //确认元素是不是本地变量的声明
        //获取变量 psi 对象
        PsiLocalVariable variable = PsiTreeUtil.getParentOfType(element, PsiLocalVariable.class);
        if (variable == null) {
            return;
        }
        if (!(variable.getParent() instanceof PsiDeclarationStatement)) {
            return;
        }
        PsiElement parent = variable.getParent();
        //Get psi class
        PsiClass psiClass = PsiTypesUtil.getPsiClass(variable.getType());
        //Check if there is a set method
        List<PsiMethod> toGenerateMethods = isWithMethod() ? PsiClassUtils.extractWithMethods(psiClass) : PsiClassUtils.extractSetMethods(psiClass);

        String variableText = variable.getName();
        Document document = editor.getDocument();
        //split text
        String splitText = PsiDocumentUtils.calculateSplitText(document, parent.getTextOffset());
        //Generate insert code
        Pair<String, Set<String>> codeAndImports = isWithMethod()
                ? generateCodeAndImports4WithMethod(toGenerateMethods, variableText, splitText)
                : generateCodeAndImports(toGenerateMethods, variableText, splitText);
        WriteCommandAction.runWriteCommandAction(project,
                () -> document.insertString(parent.getTextOffset() + parent.getTextLength(), codeAndImports.getLeft()));
        PsiToolUtils.addImportToFile(project, (PsiJavaFile) file, document, codeAndImports.getRight());

    }

    @NotNull
    private Pair<String, Set<String>> generateCodeAndImports(List<PsiMethod> allSetMethods, String variableText, String splitText) {
        StringBuilder sb = new StringBuilder();
        Set<String> importPackages = Sets.newHashSet();
        allSetMethods.forEach(psiMethod -> {
            sb.append(splitText);
            sb.append(variableText);
            sb.append('.');
            sb.append(psiMethod.getName());
            sb.append("(");
            if (hasDefaultValue()) {
                PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
                IntStream.range(0, parameters.length)
                        .mapToObj(i -> generateParameterCode(i, parameters.length, parameters[i].getType().getCanonicalText()))
                        .forEach(valueAndImport -> {
                            sb.append(valueAndImport.getLeft());
                            if (CodeUtils.isNeedToDeclareClasses(valueAndImport.getRight())) {
                                importPackages.add(valueAndImport.getRight());
                            }
                        });
            }
            sb.append(");");
        });
        return Pair.of(sb.toString(), importPackages);
    }

    @NotNull
    private Pair<String, Set<String>> generateCodeAndImports4WithMethod(List<PsiMethod> allWithMethods, String variableText, String splitText) {
        StringBuilder sb = new StringBuilder();
        Set<String> importPackages = Sets.newHashSet();
        AtomicInteger rowIndex = new AtomicInteger();
        allWithMethods.forEach(psiMethod -> {
            sb.append(splitText);
            rowIndex.getAndIncrement();
            if (rowIndex.get() == 1) {//链式调用时，第一行需要加变量名
                sb.append(variableText);
            }
            sb.append('.');
            sb.append(psiMethod.getName());
            sb.append("(");
            if (hasDefaultValue()) {
                PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
                IntStream.range(0, parameters.length)
                        .mapToObj(i -> generateParameterCode(i, parameters.length, parameters[i].getType().getCanonicalText()))
                        .forEach(valueAndImport -> {
                            sb.append(valueAndImport.getLeft());
                            if (CodeUtils.isNeedToDeclareClasses(valueAndImport.getRight())) {
                                importPackages.add(valueAndImport.getRight());
                            }
                        });
            }
            sb.append(")");
        });
        sb.append(";");
        return Pair.of(sb.toString(), importPackages);
    }

    public Pair<String, String> generateParameterCode(Integer index, int size, String parameterClassName) {
        StringBuilder code = new StringBuilder();
        Parameter parameter = PsiToolUtils.extraParameterFromFullyQualifiedName(parameterClassName);
        String packagePath = parameter.getPackagePath();
        Pair<String, String> valueAndImport = (grantRandomValue() ? CodeUtils.getRandomValueAndDefaultImport(packagePath) : CodeUtils.getDefaultValueAndDefaultImport(packagePath))
                .orElse(Pair.of("new " + parameter.getClassName() + "()", packagePath));
        code.append(valueAndImport.getLeft());
        if (index != size - 1) {
            code.append(',');
        }
        return Pair.of(code.toString(), valueAndImport.getRight());
    }

    @Override
    public void update(@NotNull AnActionEvent e) {
        e.getPresentation().setVisible(isAvailable(e));
    }

    private Boolean isAvailable(AnActionEvent e) {
        Editor editor = e.getData(PlatformDataKeys.EDITOR);
        PsiFile file = e.getData(PlatformDataKeys.PSI_FILE);
        if (editor == null || file == null) {
            return false;
        }
        PsiElement element = PsiElementUtils.getElement(editor, file);
        PsiLocalVariable variable = PsiTreeUtil.getParentOfType(element, PsiLocalVariable.class);
        if (variable == null) {
            return false;
        }
        if (!(variable.getParent() instanceof PsiDeclarationStatement)) {
            return false;
        }
        PsiClass psiClass = PsiTypesUtil.getPsiClass(variable.getType());
        boolean hasValidSetMethod = isWithMethod() ? PsiClassUtils.checkClassHasValidWithMethod(psiClass) : PsiClassUtils.checkClassHasValidSetMethod(psiClass);
        if (!hasValidSetMethod) {
            return false;
        }
        return true;
    }


}
